#include "Entity.h"
#include "GLState.h"
#include "Camera.h"

void Entity::install(const CString& shaderName) {
	ASSERT(renderOrder() != RenderOrder::INVALID, "must set RenderOrder!");

	m_shader.install(shaderName);
	m_uniform = m_shader.uniformRef();
	if (!m_2dVertices.empty())
		m_shader.prepareVABO(GLSLVarName::Vertex, m_2dVertices);
	else
		m_shader.prepareVABO(GLSLVarName::Vertex, m_vertices);
	if (!m_instanced2dVertices.empty())
		m_shader.prepareVABO(GLSLVarName::InstancedVertex, m_instanced2dVertices);
	else
		m_shader.prepareVABO(GLSLVarName::InstancedVertex, m_instancedVertices);
	m_shader.prepareEBO(m_indices);
	m_shader.prepareVABO(GLSLVarName::Normal, m_normals);
	m_shader.prepareVABO(GLSLVarName::Texcoord, m_texcoords);
}

void Entity::update() {
	if (m_vertices.empty() && m_2dVertices.empty()) {// no valid vertices, invalid, just jump:do nothing!
		Camera::get()->recalculateIfNeeded();// in case: adjust camera arguments dynamically, in some condition, vertices is empty, but camera's arguments should change indeed!
		return;
	}
	/**
	* @brief switch to my shader program(including all states: uniforms/vao vbo ebos...)!
	* @note abuse will leads to ongoing rendering and setting async problems! call glUseProgram only this place!
	* @note each uniform varying's set should link to a used shader program!
	*/
	m_shader.use();

	applyMVPMatrix();

	applyLight(Light::Type::Directional);

	applyMaterial();

	applyFadeOut();

	set2dVertexY();

	setStandaloneUniforms();

	setPreGLState();
	m_shader.draw(m_primitiveType);
	setPostGLState();
}

void Entity::applyMVPMatrix() {
	/** @note for global uniforms has dirty status; for each entity should'nt, otherwise, state be overrided(because of same uniform name) */
	const auto& V = Camera::get()->viewMatrix();
	const auto& P = Camera::get()->projectionMatrix();
	/** @note must call every render round, because of different entity has different state */
	const auto& M = m_transform.modelMatrix();
	const auto MV = V * M;
	m_uniform.add(Enum_toStr(GLSLVarName::MVP), P * MV);
	if (EntityType::MODEL == type()) {
		m_uniform.add(Enum_toStr(GLSLVarName::M), M);
		m_uniform.add(Enum_toStr(GLSLVarName::V), V);
		m_uniform.add(Enum_toStr(GLSLVarName::MVInvTr), glm::transpose(glm::inverse(MV)));
	}
}

void Entity::set2dVertexY() {
	if (!m_2dVertices.empty())
		m_uniform.add(Enum_toStr(GLSLVarName::VertexY), m_2dVertexY);
	if (!m_instanced2dVertices.empty())
		m_uniform.add(Enum_toStr(GLSLVarName::InstancedVertexY), m_instanced2dVertexY);
}

void Entity::setStandaloneUniforms() {
	for (const auto& kv : m_standaloneUniformKVs)
		m_uniform.add(kv.key, kv.value);
	for (const auto& kv : m_standaloneUniformArrayKVs)
		m_uniform.addArray(kv.key, kv.value);

	m_uniform.setSampler2D(m_sampler2DArg);
}

void Entity::setPreGLState() {
	if (!m_cullFaceEnabled || (PrimitiveType::TRIANGLE_STRIP == m_primitiveType || PrimitiveType::TRIANGLE_FAN == m_primitiveType))
		GLState::disableCullFace();
	else
		GLState::enableCullFace();

	auto _renderOrder = renderOrder();
	if (RenderOrder::AfterZhiche == _renderOrder)
		GLState::disableDepthTest();
	else if (RenderOrder::Opaque == _renderOrder || RenderOrder::ZhicheAsLast == _renderOrder)
		GLState::disableBlend();
	else// has transparency
		GLState::disableDepthWrite();
}

/**
 * @note during the rendering process, the OpenGL state may be changed by other operations.
 * @note restore all OpenGL states to default is the best practice
 */
void Entity::setPostGLState() {
	GLState::enableDepthTest();
	GLState::enableBlend();
	GLState::enableDepthWrite();
}

void Entity::applyMaterial() {
	if (m_pureColor.isValid())
		m_uniform.add("color", m_pureColor);
	else
		m_material.setAsUniform(m_uniform, m_lightEnabled);
}

void Entity::applyLight(Light::Type type) {
	if (!m_lightEnabled)
		return;
	if (!m_isLightDirty)
		return;
	switch (type) {
		case Light::Type::Directional: Light::Directional::get()->setAsUniform(m_uniform); break;
		case Light::Type::Point: Light::Point::get()->setAsUniform(m_uniform); break;
		case Light::Type::Spot: Light::Spot::get()->setAsUniform(m_uniform); break;
	}
	m_isLightDirty = false;
}

void Entity::applyFadeOut() {
	if (!m_fadeOutEdgeEnabled)
		return;
	m_uniform.addStruct("view3d",
		Field("ssaa", SSAA),
		Field("w", W3D),
		Field("h", H3D),
		Field("fadeOffsetHorizonPx", Config::View3D_FadeOffsetHorizonPx),
		Field("fadeOffsetSkylinePx", Config::View3D_FadeOffsetSkylinePx),
		Field("skylineFromTopPx", Config::View3D_SkylineFromTopPx)
	);
	bool enableSelfFade = m_fadeOutEndZ != -1;
	m_uniform.add("enableSelfFade", enableSelfFade);
	if (enableSelfFade) {
		m_uniform.add("fadeEndZ", m_fadeOutEndZ);
		m_uniform.add("fadeZOffsetPx", m_fadeOutZOffsetPx);
	}
}